<?php
def("amicitia/arquitectura/excepciones/","MetodoDeAccionInexistenteException");
def("amicitia/arquitectura/excepciones/","MapeoInexistenteException");
def("amicitia/arquitectura/resultados/","Resultado");
def("amicitia/arquitectura/seguridad/","Guardian");
def("amicitia/arquitectura/errores/","Recuperador");
def("amicitia/arquitectura/transaccional/","Transaccion");

/**
 * ProcesadorComandos se encarga de procesar comandos de tal manera que la
 * Accion especificada se ejecute con el método especificado a demás d
 * inyectar los valores de entrada por GET y por POST en la Accion en base a
 * sus nombres.
 *
 * @author Willy A. Kuster
 */
class ProcesadorComandos {

	private $comando;
	private $mapaAcciones;
	private $mapaResultados;
	private $accion;

	public function __construct($_comando = null) {
		$this->comando = $_comando;
		$this->mapaAcciones = conf("acciones","mapeos");
		$this->mapaResultados = conf("resultados","mapeos");
	}

	/**
	 * Procesa un comando buscando el mapeo de la acción correspondiente,
	 * ejecutando el método específico de dicha acción y luego
	 * invocando el resultado que la acción devuelva.
	 */
	public function procesarComando() {

		$mapeoAccion = $this->comando->getMapeoAccion();
		$nombreMetodo = $this->comando->getNombreMetodo();
		
		Recuperador::getRecuperador()->verificarMapeoError($mapeoAccion);
		
		if(array_key_exists($mapeoAccion,$this->mapaAcciones)) {
			$nombreClaseAccion = $this->mapaAcciones[$mapeoAccion]["clase"];
			$rutaAccion = $this->mapaAcciones[$mapeoAccion]["ruta"];

			$mapeoResultado = $this->ejecutarAccion($rutaAccion,$nombreClaseAccion,$nombreMetodo);

			$tipoResultado = $this->mapaResultados[$nombreClaseAccion][$mapeoResultado]["tipo"];
			$valorResultado = $this->mapaResultados[$nombreClaseAccion][$mapeoResultado]["valor"];
			$resultado = Resultado::getResultado($tipoResultado,$this->accion);
			$resultado->mostrarVista($valorResultado);

		} else {
			throw new MapeoInexistenteException($mapeoAccion);
		}

	}
	
	/**
	 * Ejecuta una acción determinada.
	 * 
	 * @param $rutaAccion
	 * 			La ruta del archivo donde se define la acción.
	 * @param $nombreClaseAccion
	 * 			El nombre de la clase de la acción.
	 * @param $nombreMetodo
	 * 			El nombre del método de la acción a ejecular.
	 * @return string
	 * 			El mapeo al resultado que debe suceder a la acción.
	 */
	public function ejecutarAccion($rutaAccion,$nombreClaseAccion,$nombreMetodo){
		def($rutaAccion,$nombreClaseAccion);

		$reflectionAccion = new ReflectionClass($nombreClaseAccion);
		$accion = $reflectionAccion->newInstance();

		$this->asignarParametrosAccion($reflectionAccion, $accion);
		
		if ($nombreMetodo == null) {
			$nombreMetodo = "hacer";
		}
		
		if ($reflectionAccion->hasMethod($nombreMetodo)) {
			$metodo = $reflectionAccion->getMethod($nombreMetodo);
			Guardian::getGuardian($accion)->autorizar($metodo);
			
			$transaccion = Transaccion::getTransaccion();
			$transaccion->analizar($metodo);
			try {
				$mapeoResultado = $metodo->invoke($accion);
				$transaccion->finalizar();
			} catch(Exception $e){
				$transaccion->truncar();
				throw $e;
			}
		} else {
			throw new MetodoDeAccionInexistenteException($nombreClaseAccion, $nombreMetodo);
		}
		
		$this->accion = $accion;
		
		return $mapeoResultado;
	}

	/**
	 * Busca por coincidencias entre los valores $_GET/$_POST y las propiedades
	 * que posean un setter en la acción que se pase por parámetro.
	 *
	 * @param $reflectionAccion
	 * 			Instancia de un ReflectionClass de la acción.
	 * @param $accion
	 * 			Intancia de la acción.
	 */
	private function asignarParametrosAccion($reflectionAccion, $accion) {

		if(!empty($_GET)) {
			foreach(array_keys($_GET) as $key) {
				$this->inyectarValor($_GET[$key], $key, $reflectionAccion, $accion);
			}
		}
		if(!empty($_POST)) {
			foreach(array_keys($_POST) as $key) {
				$this->inyectarValor($_POST[$key], $key, $reflectionAccion, $accion);
			}
		}

	}

	/**
	 * Inyecta el valor que se pase por parámetro en la propiedad con el nombre
	 * que se pase por parámetro.
	 * 
	 * @param $valor
	 * 			El valor.
	 * @param $nombrePropiedad
	 * 			El nombre de la propiedad.
	 * @param $reflectionAccion
	 * 			Instancia de un ReflectionClass de la acción.
	 * @param $accion
	 * 			Intancia de la acción.
	 */
	private function inyectarValor($valor, $nombrePropiedad, $reflectionAccion, $accion) {
		$nombreSetter = "set".ucfirst($nombrePropiedad);
		if($reflectionAccion->hasMethod($nombreSetter)) {
			$reflectionAccion->getMethod($nombreSetter)->invoke($accion, $valor);
		}
	}
	
	public function getAccion() {
		return $this->accion;
	}

}
?>